Android take screenshot on rooted device
Asked Answered
T

3

9

UPDATE There are a number of other posts asking how to get a Screenshot in android but none seemed to have a full answer of how to do so. Originally I posted this as a question due to a particular issue I was running into while attempting to open a stream to the Frame Buffer. Now I've swapped over to dumping the Frame Buffer to a file so I've updated my post to show how I got there. For reference (and acknowledgement), I found the command to send the FrameBuffer to a file from this post (unfortunately he didn't provide how he got to that point). I'm just missing how to turn the raw data I pulled from the Frame Buffer into an actual image file.

My intention was to take a full dump of the actual screen on an Android Device. The only way I could find to do so without using the adb bridge was to directly access the Frame Buffer of the system. Obviously this approach will require root privileges on the device and for the app running it! Fortunately for my purposes I have control over how the Device is set up and having the device rooted with root privileges provided to my application is feasible. My testing is currently being done on an old Droid running 2.2.3.

I found my first hints of how to approach it from https://mcmap.net/q/954312/-android-how-can-you-get-framebuffer-screenshot-on-rooted-device. After a bit more research I found another article that describes how to properly run shell commands as root. They were using it to execute a reboot, I use it to send the current frame buffer to an actual file. My current testing has only gotten as far as doing this via ADB and in a basic Activity (each being provided root). I will be doing further testing from a Service running in the background, updates to come! Here is my entire test activity that can export the current screen to a file:

public class ScreenshotterActivity extends Activity {
    public static final String TAG = "ScreenShotter";

    private Button _SSButton;
    private PullScreenAsyncTask _Puller;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);


        _SSButton = (Button)findViewById(R.id.main_screenshotButton);
        _SSButton.setOnClickListener(new OnClickListener() {

            public void onClick(View v) {
                if (_Puller != null)
                    return;
                //TODO: Verify that external storage is available! Could always use internal instead...

                _Puller = new PullScreenAsyncTask();
                _Puller.execute((Void[])null);
            }
        });
    }

    private void runSuShellCommand(String cmd) {
        Runtime runtime = Runtime.getRuntime();
        Process proc = null;
        OutputStreamWriter osw = null;
        StringBuilder sbstdOut = new StringBuilder();
        StringBuilder sbstdErr = new StringBuilder();

        try { // Run Script
            proc = runtime.exec("su");
            osw = new OutputStreamWriter(proc.getOutputStream());
            osw.write(cmd);
            osw.flush();
            osw.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            if (osw != null) {
                try {
                    osw.close();
                } catch (IOException e) {
                    e.printStackTrace();                    
                }
            }
        }

        try {
            if (proc != null)
                proc.waitFor();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        sbstdOut.append(readBufferedReader(new InputStreamReader(proc.getInputStream())));
        sbstdErr.append(readBufferedReader(new InputStreamReader(proc.getErrorStream())));
    }

    private String readBufferedReader(InputStreamReader input) {

        BufferedReader reader = new BufferedReader(input);
        StringBuilder found = new StringBuilder();
        String currLine = null;
        String sep = System.getProperty("line.separator");
        try {
            // Read it all in, line by line.
            while ((currLine = reader.readLine()) != null) {
                found.append(currLine);
                found.append(sep);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return null;
    }

    class PullScreenAsyncTask extends AsyncTask<Void, Void, Void> {

        @Override
        protected Void doInBackground(Void... params) {

            File ssDir = new File(Environment.getExternalStorageDirectory(), "/screenshots");
            if (ssDir.exists() == false) {
                Log.i(TAG, "Screenshot directory doesn't already exist, creating...");
                if (ssDir.mkdirs() == false) {
                    //TODO: We're kinda screwed... what can be done?
                    Log.w(TAG, "Failed to create directory structure necessary to work with screenshots!");
                    return null;
                }
            }
            File ss = new File(ssDir, "ss.raw");            
            if (ss.exists() == true) {
                ss.delete();
                Log.i(TAG, "Deleted old Screenshot file.");
            }
            String cmd = "/system/bin/cat /dev/graphics/fb0 > "+ ss.getAbsolutePath();
            runSuShellCommand(cmd);
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result);
            _Puller = null;
        }
    }
}

This also requires adding the android.permission.WRITE_EXTERNAL_STORAGE permission to the Manifest. As suggested in this post. Otherwise it runs, doesn't complain, doesn't create the directories nor the file.

Originally I couldn't get usable data from the Frame Buffer due to not understanding how to properly run shell commands. Now that I've swapped to using the streams for executing commands I can use '>' to send the Frame Buffer's current data to an actual file...

Ternopol answered 9/6, 2012 at 23:46 Comment(3)
Thank you for your coding. However, the .raw file might have a problem. I tried to open the .raw image file with google picassa, but it said there is some part picassa can not read. Do you know what's the problem? How can I get normal image file from it? Thanks.Redoubtable
@swk This isn't a simple question to answer. My requirements were more in the realm of determining the differences between sequential screen shots so I haven't fully determined how to turn them back into images! I would suggest a thorough search of the rest of Stack Overflow specifically for how to turn the data found at /dev/graphics/fb0 into an image.Ternopol
@swk Have you found any way of coverting framebuffer into an image file??Burbot
R
3

That would be different for different phones. It depends on the underlying graphics format of your device. You can poll what the graphics format is using system calls. If you are only going to run this on devices that you know the graphics format of you can write a converter that turns it into a known format.

You can have a look at the following project: http://code.google.com/p/android-fb2png/

If you look at the source code for fb2png.c you can see that they poll FBIOGET_VSCREENINFO which contains info about how the device stores the screen image in memory. Once you know that, you should be able to convert it into a format you can use.

I hope this helps.

Recondite answered 19/7, 2012 at 11:29 Comment(1)
That does look like the most likely route to solve this problem. As there doesn't appear to be any further interest in this topic, I'm marking this as the answer. It would really be nice if someone completely solved this, but I'm unfortunately rather swamped with other things right now!Ternopol
I
10

Programmatically you can run "adb shell /system/bin/screencap -p /sdcard/img.png" as below :

Process sh = Runtime.getRuntime().exec("su", null,null);
OutputStream  os = sh.getOutputStream();
os.write(("/system/bin/screencap -p " + "/sdcard/img.png").getBytes("ASCII"));
os.flush();
os.close();
sh.waitFor();
Inseverable answered 4/3, 2013 at 18:24 Comment(11)
Its not working .. Its returning NULL . even I have set the permission <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />Effulgent
Which android version are you usingInseverable
its KitKat (4.4) @ ViswanathEffulgent
This will work only in rooted phone. Can you check whether, you have such a file at location "/system/bin/screencap"Inseverable
My device is rooted one .. It has "system/bin/screencap" file and when I am doing this with adb shell I am getting the snapshot , but when i am trying to do this programatically, its not working and returning NULL.Effulgent
Ok, That you didn't explain,Actually kitkat has a problem with <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> can you please refer to the issues regarding this permission on kitkatInseverable
@ Viswanath L - I think I found the problem : KNOX is installed in my device , which is not allowing any app to get root access ..Effulgent
Works great for GoogleTangoSeaborne
what is the purpose of using sh.waitFor(): ? Also, this code worked for me, but I want to take another screenshot after say 10 seconds or so, but when I try again, it does not work.Tarrasa
Nevermind: It works perfectly on my rooted device. Thank you!Tarrasa
It's not working when "taking screenshots is not allowed by the app or your organization"Vocal
A
5

An easy solution for ICS devices is to use the following from the command line

adb shell /system/bin/screencap -p /sdcard/screenshot.png
adb pull /sdcard/screenshot.png screenshot.png

This'll save the screenshot.png file in the current directory.

Tested on a Samsung Galaxy SII running 4.0.3.

Accelerator answered 3/8, 2012 at 15:44 Comment(1)
This question wasn't related to working from a separate machine. This was purely for an on-device, in the field situation. ADB is nice, but doesn't do me any good in this situation.Ternopol
R
3

That would be different for different phones. It depends on the underlying graphics format of your device. You can poll what the graphics format is using system calls. If you are only going to run this on devices that you know the graphics format of you can write a converter that turns it into a known format.

You can have a look at the following project: http://code.google.com/p/android-fb2png/

If you look at the source code for fb2png.c you can see that they poll FBIOGET_VSCREENINFO which contains info about how the device stores the screen image in memory. Once you know that, you should be able to convert it into a format you can use.

I hope this helps.

Recondite answered 19/7, 2012 at 11:29 Comment(1)
That does look like the most likely route to solve this problem. As there doesn't appear to be any further interest in this topic, I'm marking this as the answer. It would really be nice if someone completely solved this, but I'm unfortunately rather swamped with other things right now!Ternopol

© 2022 - 2024 — McMap. All rights reserved.