Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

take screenshot which follow minicap protocol #8

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion app/src/main/java/jp/co/cyberagent/stf/Agent.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@
import android.view.KeyEvent;
import android.view.Surface;

import com.google.protobuf.ByteString;

import java.io.IOException;
import java.io.OutputStream;
import java.net.UnknownHostException;

import jp.co.cyberagent.stf.compat.InputManagerWrapper;
import jp.co.cyberagent.stf.compat.PowerManagerWrapper;
import jp.co.cyberagent.stf.compat.ScreenshotManagerWrapper;
import jp.co.cyberagent.stf.compat.WindowManagerWrapper;
import jp.co.cyberagent.stf.proto.Wire;
import jp.co.cyberagent.stf.util.InternalApi;
Expand All @@ -25,6 +29,7 @@ public class Agent {
private InputManagerWrapper inputManager;
private PowerManagerWrapper powerManager;
private WindowManagerWrapper windowManager;
private ScreenshotManagerWrapper screenshotManager;
private LocalServerSocket serverSocket;
private int deviceId = -1; // KeyCharacterMap.VIRTUAL_KEYBOARD
private KeyCharacterMap keyCharacterMap;
Expand Down Expand Up @@ -100,6 +105,7 @@ private void run() {
powerManager = new PowerManagerWrapper();
inputManager = new InputManagerWrapper();
windowManager = new WindowManagerWrapper();
screenshotManager = new ScreenshotManagerWrapper();

selectDevice();
loadKeyCharacterMap();
Expand Down Expand Up @@ -189,7 +195,7 @@ public void run() {
System.err.println("InputClient started");

try {
while (!isInterrupted()) {
while (!isInterrupted() && !clientSocket.isClosed()) {
Wire.Envelope envelope =
Wire.Envelope.parseDelimitedFrom(clientSocket.getInputStream());

Expand All @@ -210,6 +216,9 @@ public void run() {
case SET_ROTATION:
handleSetRotationRequest(envelope);
break;
case DO_SCREENSHOT:
handleScreenshotRequest(envelope);
break;
default:
System.err.printf("Unknown request type %d; maybe it's a Service call?\n", envelope.getType());
}
Expand Down Expand Up @@ -309,6 +318,11 @@ private void handleSetRotationRequest(Wire.Envelope envelope) throws IOException
}
}

private void handleScreenshotRequest(Wire.Envelope envelope) throws IOException {
Wire.DoScreenshotRequest request = Wire.DoScreenshotRequest.parseFrom(envelope.getMessage());
screenshot();
}

private void keyDown(int keyCode, int metaState) {
long time = SystemClock.uptimeMillis();
inputManager.injectKeyEvent(new KeyEvent(
Expand Down Expand Up @@ -367,5 +381,25 @@ private void freezeRotation(int rotation) {
private void thawRotation() {
windowManager.thawRotation();
}

private void screenshot() {
try {
byte[] bmp = screenshotManager.screenshot();
if (bmp == null) {
return;
}
OutputStream out = clientSocket.getOutputStream();
out.write(Wire.GetScreenshotResponse.newBuilder()
.setSuccess(true)
.setScreenshot(ByteString.copyFrom(bmp))
.build().toByteArray());

out.flush();
out.close();
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
144 changes: 144 additions & 0 deletions app/src/main/java/jp/co/cyberagent/stf/Capture.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package jp.co.cyberagent.stf;

import android.net.LocalServerSocket;
import android.net.LocalSocket;

import java.io.IOException;
import java.io.OutputStream;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import jp.co.cyberagent.stf.compat.ScreenshotManagerWrapper;
import jp.co.cyberagent.stf.util.ProcUtil;

public class Capture {
public static final String PROCESS_NAME = "stf.capture";
public static final String SOCKET = "javacap";

private LocalServerSocket serverSocket;

public Capture() {
}

public static void main(String[] args) {
ProcUtil.setArgV0(PROCESS_NAME);

for (String arg : args) {
if (arg.equals("--version")) {
System.out.println(Version.name);
return;
} else {
System.err.println("Error: unknown argument " + arg);
System.exit(1);
}
}

new Capture().run();
}

private void run() {
startServer();
waitForClients();
}

private void startServer() {
try {
serverSocket = new LocalServerSocket(SOCKET);
System.err.printf("Listening on @%s\n", SOCKET);
} catch (UnknownHostException e) {
e.printStackTrace();
System.exit(1);
} catch (IOException e) {
stopServer();
e.printStackTrace();
System.exit(1);
}
}

private void stopServer() {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}


private void waitForClients() {
while (true) {
try {
LocalSocket clientSocket = serverSocket.accept();
Capture.InputClient client = new Capture.InputClient(clientSocket);
client.start();
} catch (IOException e) {
e.printStackTrace();
break;
}
}
}

private class InputClient extends Thread {
private LocalSocket clientSocket;
private ScreenshotManagerWrapper screenshotManager;

public InputClient(LocalSocket clientSocket) {
this.clientSocket = clientSocket;
}

@Override
public void interrupt() {
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public void run() {
System.err.println("InputClient started");

screenshotManager = new ScreenshotManagerWrapper();

try {

OutputStream out = clientSocket.getOutputStream();
byte[] jpegData = screenshotManager.screenshot();
ByteBuffer buf = ByteBuffer.allocate(24);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.put((byte) 0x01); // version
buf.put((byte) 0x03); // length 3*8 bit
buf.putInt(0); // pid
buf.putInt(screenshotManager.getWidth()); // real width
buf.putInt(screenshotManager.getHeight()); // real height
buf.putInt(0); // virt width
buf.putInt(0); // virt height
buf.put((byte) 0x00); // display orientation
buf.put((byte) 0x01); // quirk: QUIRK_DUMB
buf.flip();

out.write(buf.array());
out.flush();

while (true) {
// jpeg data size
buf = ByteBuffer.allocate(4);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.putInt(jpegData.length);
buf.flip();
out.write(buf.array());
// jpeg data
out.write(jpegData);
out.flush();
jpegData = screenshotManager.screenshot();
}
} catch (IOException e) {
System.out.println("I/O exception: " + e);
}
System.err.println("InputClient closing");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package jp.co.cyberagent.stf.compat;

import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.view.IWindowManager;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;


/**
* Created by Stanley Huang on 2016/11/21.
*/
public class ScreenshotManagerWrapper {

private static String SURFACE_CONTROL_CLASS = "android.view.SurfaceControl";
private static String SURFACE_CLASS = "android.view.Surface";
private Method injector;
private final IWindowManager wm;
private Bitmap lastBitmap;

public ScreenshotManagerWrapper() {
IBinder wmbinder = ServiceManager.getService("window");
wm = IWindowManager.Stub.asInterface(wmbinder);
if (Build.VERSION.SDK_INT <= 17) {
injector = new OldApiScreenshotInjector().injectorMethod();
} else {
injector = new NewApiScreenshotInjector().injectorMethod();
}
}

private interface ScreenshotInjector {
public Method injectorMethod();
}

public int getWidth() {
return lastBitmap.getWidth();
}

public int getHeight() {
return lastBitmap.getHeight();
}

public byte[] screenshot() {
byte[] bmpArray = null;
try {
int rotation = wm.getRotation();
Matrix m = new Matrix();
if (rotation == 1) {
m.postRotate(-90.0f);
} else if (rotation == 2) {
m.postRotate(-180.0f);
} else if (rotation == 3) {
m.postRotate(-270.0f);
}
Bitmap bmp = (Bitmap) injector.invoke(null, new Object[]{Integer.valueOf(0), Integer.valueOf(0)});
if (rotation != 0) {
bmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, false);
}
lastBitmap = bmp;
ByteArrayOutputStream bout = new ByteArrayOutputStream();
bmp.compress(Bitmap.CompressFormat.JPEG, 75, bout);
bmpArray = bout.toByteArray();
} catch (RemoteException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return bmpArray;
}


private class NewApiScreenshotInjector implements ScreenshotInjector {
private Method injector;

public Method injectorMethod() {
try {
injector = Class.forName(SURFACE_CONTROL_CLASS).getDeclaredMethod("screenshot",
new Class[]{Integer.TYPE, Integer.TYPE});
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return injector;
}
}

private class OldApiScreenshotInjector implements ScreenshotInjector {
private Method injector;

public Method injectorMethod() {
try {
injector = Class.forName(SURFACE_CLASS).getDeclaredMethod("screenshot",
new Class[]{Integer.TYPE, Integer.TYPE});
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return injector;
}
}

}
9 changes: 9 additions & 0 deletions proto/src/main/proto/wire.proto
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ enum MessageType {
DO_WAKE = 4;
DO_ADD_ACCOUNT_MENU = 24;
DO_REMOVE_ACCOUNT = 20;
DO_SCREENSHOT = 30;
GET_ACCOUNTS = 26;
GET_BROWSERS = 5;
GET_CLIPBOARD = 6;
Expand Down Expand Up @@ -293,3 +294,11 @@ message SetRotationRequest {

message DoWakeRequest {
}

message DoScreenshotRequest {
}

message GetScreenshotResponse {
required bool success = 1;
required bytes screenshot = 2;
}