Android - Display a screen recording on a SurfaceView

756 Views Asked by At

I am developing an application that can record the contents of an Android device's screen and display it on a SurfaceView. As of now, the SurfaceView is a box in the middle of the screen and is currently showing the contents of the entire screen, along with itself, creating a repeating image. Is there a way to repeatedly hide the SurfaceView, create a virtual display, then show the SurfaceView with the contents of the virtual display?

RecordingSession.java

class RecordingSession
  implements MediaScannerConnection.OnScanCompletedListener {
  static final int VIRT_DISPLAY_FLAGS=
    DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY |
      DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
  private RecordingConfig config;
  private final File output;
  private final Context ctxt;
  private final ToneGenerator beeper;
  private MediaRecorder recorder;
  private MediaProjection projection;
  private VirtualDisplay vdisplay;
  private Window window;

  RecordingSession(Context ctxt, RecordingConfig config,
                   MediaProjection projection, Window window) {
    this.ctxt=ctxt.getApplicationContext();
    this.window = window;
    this.config=config;
    this.projection=projection;
    this.beeper=new ToneGenerator(
      AudioManager.STREAM_NOTIFICATION, 100);

    output=new File(ctxt.getExternalFilesDir(null), "andcorder.mp4");
    output.getParentFile().mkdirs();
  }

  void start() {

      //this.window.close();
    vdisplay=projection.createVirtualDisplay("andcorder",
            config.width, config.height, config.density,
            VIRT_DISPLAY_FLAGS, this.window.getScreenShot().getHolder().getSurface(), null, null);
    //this.window.open();
    beeper.startTone(ToneGenerator.TONE_PROP_ACK);
  }

  void stop() {
    projection.stop();
    vdisplay.release();
  }

  @Override
  public void onScanCompleted(String path, Uri uri) {
    beeper.startTone(ToneGenerator.TONE_PROP_NACK);
  }
}

Window.java

public class Window {

    // declaring required variables
    private Context context;
    private View mView;
    private WindowManager.LayoutParams mParams;
    private WindowManager mWindowManager;
    private LayoutInflater layoutInflater;

    public SurfaceView getScreenShot() {
        return screenShot;
    }

    private SurfaceView screenShot;
    private LinearLayout toDisplay;

    public Window(Context context){
        this.context=context;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            // set the layout parameters of the window
            mParams = new WindowManager.LayoutParams(
                    // Shrink the window to wrap the content rather
                    // than filling the screen
                    WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT,
                    // Display it on top of other application windows
                    WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                    // Don't let it grab the input focus
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                    // Make the underlying application window visible
                    // through any transparent parts
                    PixelFormat.TRANSLUCENT);

        }
        // getting a LayoutInflater
        layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        // inflating the view with the custom layout we created
        mView = layoutInflater.inflate(R.layout.popup_window, null);
        screenShot= mView.findViewById(R.id.screenShot);
        
        // Define the position of the
        // window within the screen
        mParams.gravity = Gravity.CENTER;
        mWindowManager = (WindowManager)context.getSystemService(WINDOW_SERVICE);
    }

    public void open() {

        try {
            // check if the view is already
            // inflated or present in the window
            if(mView.getWindowToken()==null) {
                if(mView.getParent()==null) {
                    mWindowManager.addView(mView, mParams);
                }
            }
        } catch (Exception e) {
            Log.d("Error1",e.toString());
        }

    }

    public void hide() {
        try {
            mView.setVisibility(View.INVISIBLE);
        } catch (Exception e) {
            Log.d("Error3",e.toString());
        }
    }

    public void show() {
        try {
            mView.setVisibility(View.VISIBLE);
        } catch (Exception e) {
            Log.d("Error4",e.toString());
        }
    }

    public void close() {

        try {
            // remove the view from the window
            ((WindowManager)context.getSystemService(WINDOW_SERVICE)).removeView(mView);
            // invalidate the view
            mView.invalidate();
            // remove all views
            ((ViewGroup)mView.getParent()).removeAllViews();

            // the above steps are necessary when you are adding and removing
            // the view simultaneously, it might give some exceptions
        } catch (Exception e) {
            Log.d("Error2",e.toString());
        }
    }
}

popup_window.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:padding="4dp"
    android:background="@null">

        <SurfaceView
            android:id="@+id/screenShot"
            android:layout_width="match_parent"
            android:layout_height="250dp" />

</RelativeLayout>
1

There are 1 best solutions below

0
On

I have played around with my code and discovered that doubling the virtualdisplay's height and width did the trick. I am a beginner in android development and therefore do not know the reason as to why this worked. If someone can shed light onto why this tweak did the trick, that would be great.

vdisplay=projection.createVirtualDisplay("andcorder",
            config.width*2, config.height*2, config.density,
            VIRT_DISPLAY_FLAGS, this.window.getScreenShot().getHolder().getSurface(), null, null);