Capturing Nougat Screenshots Using adb shell
Prior to Android Nougat, I used the following bash function to speedily save screenshots to my local machine:
function screenshot() {
adb shell screencap -p | perl -pe 's/\x0D\x0A/\x0A/g' > "${HOME}/Desktop/screenshot.png"
}
However, executing this function when using a device running Android Nougat results in a corrupted output file. What gives?
Background
The output of the adb shell’s screencap utility is known to be somewhat funky on older (read: pre-Nougat) versions of Android. In particular, “adb shell” performs an automatic line feed (LF) to {carriage return (CR) + line feed (LF)} conversion. This can be observed by capturing a “naive” screenshot (no perl sanitization):
adb shell screencap -p > "screenshot.png"
and then inspecting the corresponding hex dump:
$ hexdump -C screenshot.png | head
00000000 89 50 4e 47 0d 0d 0a 1a 0d 0a 00 00 00 0d 49 48 |.PNG..........IH|
00000010 44 52 00 00 02 d0 00 00 05 00 08 06 00 00 00 6e |DR.............n|
00000020 ce 65 3d 00 00 00 04 73 42 49 54 08 08 08 08 7c |.e=....sBIT....||
00000030 08 64 88 00 00 20 00 49 44 41 54 78 9c ec bd 79 |.d... .IDATx...y|
00000040 9c 1d 55 9d f7 ff 3e 55 75 f7 de b7 74 77 d2 d9 |..U...>Uu...tw..|
00000050 bb b3 27 10 48 42 16 c0 20 01 86 5d 14 04 11 dc |..'.HB.. ..]....|
00000060 78 44 9d c7 d1 d1 11 78 70 7e 23 33 8e 1b 38 33 |xD.....xp~#3..83|
00000070 ea 2c 8c 8e 0d 0a 08 a8 23 2a 0e 10 82 ac c1 40 |.,......#*.....@|
00000080 12 02 81 24 64 ef ec 5b ef fb 5d 6b 3b bf 3f ea |...$d..[..]k;.?.|
00000090 de db dd 49 27 e9 ee 74 77 3a e3 79 bf 5e 37 e7 |...I'..tw:.y.^7.|
All valid PNG files start with the following 8-byte header:
89 50 4e 47 0d 0a 1a 0a
But look at the first 10 bytes of our “naively”-captured pre-Nougat screenshot:
89 50 4e 47 0d 0d 0a 1a 0d 0a
They don’t match! Each occurrence of the byte “0a” in the correct byte sequence has been replaced with a pair of bytes, “0d” “0a” in the incorrect byte sequencing. This is precisely the line feed (LF) to {carriage return (CR) + line feed (LF)} conversion mentioned earlier. The global perl search-and-replace in the original script reverts this heavy-handed manipulation and results in a valid PNG file.
Nougat
Here’s the hex dump of a “naively”-captured Nougat screenshot:
$ adb shell screencap -p > "screenshot.png"
$ hexdump -C screenshot.png | head
00000000 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 |.PNG........IHDR|
00000010 00 00 05 a0 00 00 0a 00 08 06 00 00 00 47 f4 85 |.............G..|
00000020 cf 00 00 00 04 73 42 49 54 08 08 08 08 7c 08 64 |.....sBIT....|.d|
00000030 88 00 00 20 00 49 44 41 54 78 9c ec dd 79 78 54 |... .IDATx...yxT|
00000040 f5 f9 fe f1 7b 96 4c b6 c9 1e f6 9d 20 06 65 09 |....{.L..... .e.|
00000050 a0 b2 c8 aa 20 b8 07 6d d5 aa 55 b4 ae b5 56 d4 |.... ..m..U...V.|
00000060 5f ad 7c ad 56 d4 6a 37 04 6c d5 2a 2a d8 82 5b |_.|.V.j7.l.**..[|
00000070 55 c0 1d 50 d9 41 ac ec c8 be 86 2d 40 42 12 b2 |U..P.A.....-@B..|
00000080 4f 66 f9 fd 11 83 42 66 26 db 4c e6 4c f2 7e 5d |Of....Bf&.L.L.~]|
00000090 17 57 cb 99 33 e7 3c 33 cc 04 bc cf e7 3c 8f 49 |.W..3.<3.....<.I|
The first 8 bytes do match the expected PNG file header, so we can deduce that “adb shell” has not performed the line feed (LF) to {carriage return (CR) + line feed (LF)} conversion in this case! This also explains why my original bash function always produces invalid Nougat screenshots. By attempting to undo a conversion that was never applied, the perl search-and-replace mutilates the PNG file header, replacing the required “0d” “0a” sequence with a single “0a”.
Solution
For now, I’ve defined separate pre-Nougat and post-Nougat screenshot bash functions to deal with this change in behavior:
function screenshot() {
adb shell screencap -p | perl -pe 's/\x0D\x0A/\x0A/g' > "${HOME}/Desktop/screenshot.png"
}
function screenshot-n() {
adb shell screencap -p > "${HOME}/Desktop/screenshot.png"
}
There’s probably a slick way to unify these functions by inspecting the incoming bytes and conditionally applying the global perl search-and-replace, but the solutions above work well enough for me.