2

These are the errors I am getting:

2021-10-31 12:18:44.281 15783-15921/com.exabes.test E/AndroidRuntime: FATAL EXCEPTION: Thread-7
    Process: com.exabes.test, PID: 15783
    java.lang.IllegalStateException: Cannot start already started MediaProjection
        at android.os.Parcel.createException(Parcel.java:1974)
        at android.os.Parcel.readException(Parcel.java:1934)
        at android.os.Parcel.readException(Parcel.java:1884)
        at android.media.projection.IMediaProjection$Stub$Proxy.start(IMediaProjection.java:144)
        at android.media.projection.MediaProjection.<init>(MediaProjection.java:69)
        at android.media.projection.MediaProjectionManager.getMediaProjection(MediaProjectionManager.java:169)
        at com.autobot.service.AbstractNotificationService.startCapture(AbstractNotificationService.java:241)
        at com.exabes.test.ExampleThread.run(ExampleThread.java:813)
     Caused by: android.os.RemoteException: Remote stack trace:
        at com.android.server.media.projection.MediaProjectionManagerService$MediaProjection.start(MediaProjectionManagerService.java:416)
        at android.media.projection.IMediaProjection$Stub.onTransact(IMediaProjection.java:52)
        at android.os.Binder.execTransact(Binder.java:739)

and:

2021-10-31 12:35:40.293 17831-17992/com.exabes.test E/AndroidRuntime: FATAL EXCEPTION: Thread-7
    Process: com.exabes.test, PID: 17831
    java.lang.NullPointerException: Attempt to invoke virtual method 'android.hardware.display.VirtualDisplay android.media.projection.MediaProjection.createVirtualDisplay(java.lang.String, int, int, int, int, android.view.Surface, android.hardware.display.VirtualDisplay$Callback, android.os.Handler)' on a null object reference
        at com.exabes.test.AbstractNotificationService.startCapture(AbstractNotificationService.java:251)
        at com.exabes.test.ExampleThread.run(ExampleThread.java:813)

I am basically requesting the createScreenCaptureIntent in the MainActivity and pass the results to the AccessebilityService where all the methods necessary to take a screenshots are declared. Then I simply call startCapture() into the Thread. I am gonna post most of the code.

MainActivity:

public class MainActivity extends Activity {

private static final int DRAW_OVER_OTHER_APP_PERMISSION = 123;

private static final int REQUEST_SCREENSHOT=59706;
private MediaProjectionManager mgr;

int resultCodeX;
Intent dataX;



@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); //TODO

    mgr=(MediaProjectionManager)getSystemService(MEDIA_PROJECTION_SERVICE);
    startActivityForResult(mgr.createScreenCaptureIntent(), REQUEST_SCREENSHOT);

    this.askPermissions();

    ...
}


public void startService()
{
        // To prevent starting the service if the required permission is NOT granted.
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(MainActivity.this)) {
            Intent intent = new Intent(MainActivity.this, ExampleService.class);
            intent.putExtra(getString(R.string.edit_text_name), Objects.requireNonNull(trainerUsernameEditText.getText()).toString());
            intent.putExtra(ExampleService.EXTRA_RESULT_CODE, resultCodeX);
            intent.putExtra(ExampleService.EXTRA_RESULT_INTENT, dataX);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
                startForegroundService(intent);
            else
                startService(intent);
            finish();
        } else {
            error();
            askForSystemOverlayPermission();
        }
}



@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {

    if (requestCode == REQUEST_SCREENSHOT) {
        if (resultCode == RESULT_OK) {
            //TODO
            resultCodeX=resultCode;
            dataX=data;
            //finish();
        }
    }

    else if (requestCode == DRAW_OVER_OTHER_APP_PERMISSION) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (!Settings.canDrawOverlays(this)) {
                //Permission is not available. Display error text.
                errorToast();
                finish();
            }
        }
    }

    else {
        super.onActivityResult(requestCode, resultCode, data);
    }
}}

AccesibilityService:

public abstract class AbstractNotificationService extends AccessibilityService {

private static final String ACTION_STOP = "STOP";
private NotificationCompat.Builder builder;
private NotificationManagerCompat notificationManager;

private ActivityManager activityManager;


private static final String CHANNEL_WHATEVER="channel_whatever";
private static final int NOTIFY_ID=9906;
public static final String EXTRA_RESULT_CODE="resultCode";
public static final String EXTRA_RESULT_INTENT="resultIntent";
static final String ACTION_RECORD=
        BuildConfig.APPLICATION_ID+".RECORD";
static final String ACTION_SHUTDOWN=
        BuildConfig.APPLICATION_ID+".SHUTDOWN";
static final int VIRT_DISPLAY_FLAGS=
        DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY |
                DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
private MediaProjection projection;
private VirtualDisplay vdisplay;
final private HandlerThread handlerThread=
        new HandlerThread(getClass().getSimpleName(),
                android.os.Process.THREAD_PRIORITY_BACKGROUND);
private Handler handler;
private MediaProjectionManager mgr;
private WindowManager wmgr;
private ImageTransmogrifier it;
private int resultCode;
private Intent resultData;
final private ToneGenerator beeper=
        new ToneGenerator(AudioManager.STREAM_NOTIFICATION, 100);



@Override
public void onCreate() {
    super.onCreate();

    activityManager = (ActivityManager) getSystemService(Activity.ACTIVITY_SERVICE);


    mgr=(MediaProjectionManager)getSystemService(MEDIA_PROJECTION_SERVICE);
    wmgr=(WindowManager)getSystemService(WINDOW_SERVICE);

    handlerThread.start();
    handler=new Handler(handlerThread.getLooper());
}



@Override
public int onStartCommand (Intent intent, int flags, int startId) {

    String action = intent.getAction();
    if (action != null) {
        if (action.equals(getString(R.string.stop_foreground_action))) {
            stopForeground(true);
            stopSelf();

            return START_NOT_STICKY;
        }
    }
    else {
        resultCode=intent.getIntExtra(EXTRA_RESULT_CODE, 1337);
        resultData=intent.getParcelableExtra(EXTRA_RESULT_INTENT);
        //foregroundify();
    }

    //return START_STICKY;
    return START_REDELIVER_INTENT;
}


@Override
public void onDestroy() {
    super.onDestroy();

    //TODO
    beeper.startTone(ToneGenerator.TONE_PROP_NACK);
    stopCapture();
}


public abstract void sendNotification(String response);


public void killBackground(String packageName)
{
    activityManager.killBackgroundProcesses(packageName);
}



public WindowManager getWindowManager() {
    return(wmgr);
}

public Handler getHandler() {
    return(handler);
}

public void processImage(final byte[] png) {
    new Thread() {
        @Override
        public void run() {
            //File output=new File(getExternalFilesDir(null),"screenshot.png");
            File output=new File(Environment.getExternalStorageDirectory(),"screenshot.png");

            try {
                FileOutputStream fos=new FileOutputStream(output);

                fos.write(png);
                fos.flush();
                //fos.getFD().sync();
                fos.close();

                MediaScannerConnection.scanFile(AbstractNotificationService.this,
                        new String[] {output.getAbsolutePath()},
                        new String[] {"image/png"},
                        null);
            }
            catch (Exception e) {
                Log.e(getClass().getSimpleName(), "Exception writing out screenshot", e);
            }
        }
    }.start();

    beeper.startTone(ToneGenerator.TONE_PROP_ACK);
    stopCapture();
}

private void stopCapture() {
    if (projection!=null) {
        projection.stop();
        vdisplay.release();
        projection=null;
    }
}

public void startCapture() {
    projection=mgr.getMediaProjection(resultCode, resultData);
    it=new ImageTransmogrifier(this);

    MediaProjection.Callback cb=new MediaProjection.Callback() {
        @Override
        public void onStop() {
            vdisplay.release();
        }
    };

    vdisplay=projection.createVirtualDisplay("andshooter",
            it.getWidth(), it.getHeight(),
            getResources().getDisplayMetrics().densityDpi,
            VIRT_DISPLAY_FLAGS, it.getSurface(), null, handler);
    projection.registerCallback(cb, handler);
}}

Thread:

public class ExampleThread extends Thread {

private AbstractNotificationService notificationService;


public ExampleThread(AbstractNotificationService notificationService)
{
    this.notificationService = notificationService;
}


@Override
public void run()
{
    
    while(boolean){
        ...
        notificationService.startCapture();
    }
    notificationService.sendNotification("ENDED");
}}
DeborahAnn
  • 191
  • 1
  • 11
  • Your first problem feels like the explanation on the exception in [the `getMediaProjection()` docs](https://developer.android.com/reference/android/media/projection/MediaProjectionManager#getMediaProjection(int,%20android.content.Intent)): "on pre-Q devices if a previously gotten MediaProjection from the same resultData has not yet been stopped". The second problem is that `getMediaProjection()` is returning `null`, which isn't documented, but might be if the credentials are stale. Make sure that you use the credentials once and reasonably quickly after you get them. – CommonsWare Nov 01 '21 at 20:54
  • (where by "credentials", I mean whatever it is in the result `Intent` from the permission dialog that allows your app to do a recording) – CommonsWare Nov 01 '21 at 20:55
  • @CommonsWare about my first problem, as you can see I am calling the startCapture() method whenever I need it from the Thread; so I should I stop it every single time in the thread as well? I would prefer to simply take a screenshot whenever I need it and then stop the gotten MediaProjection only at the end. About my second problem, the credentials should never be stale since I use START_REDELIVER_INTENT but I'll try to handle this on the onStartCommand. – DeborahAnn Nov 12 '21 at 15:44
  • "I would prefer to simply take a screenshot whenever I need it and then stop the gotten MediaProjection only at the end" -- AFAIK, that would be OK, but you need to make sure that you do not try starting the media projection again. Your error suggests that this is happening. – CommonsWare Nov 12 '21 at 15:51
  • I am not sure how, I am not starting the media projection again since I made sure to the onStartCommand of the AccessibilityService that code is executed only if coming from MainActivity. Should I change something on the startCapture method? Also, another question. I've noticed that startCapture() it's not a blocking call - meaning the code executed right after might not find the screenshot. How can I fix this? – DeborahAnn Nov 13 '21 at 10:30
  • "How can I fix this?" -- there should be a callback method that you can use to know when it is ready. In all seriousness, I have not fussed with the media projection APIs in quite some time, so my ability to help you is somewhat limited. – CommonsWare Nov 13 '21 at 13:11

0 Answers0