Table of Contents
- 1. WebApp Directory structure
- 2. Sample App
- 3. Dreamhost servers
- 4. Offline
- 5. Crazy Maik
- 6. Actions and Commands
- 7. session fragment
- 8. Network
- 9. functionality
- 10. printing
- 11. HitiPPR_GetPrinterInfo
- 12. PrintingPhotoUtility
- 13. Network Discovery
- 14. update yii apps
- 15. ACT I. Scene I. Verona. A...
- 16. Scene II. A Street.
- 17. Scene III. Capulet's house.
- 18. Scene IV. A street.
- 19. Scene V. Capulet's house.
- 20. PROLOGUE
- 21. ACT II. Scene I. A lane b...
- 22. Scene II. Capulet's orchard.
- 23. Scene III. Friar Laurence...
- 24. Scene IV. A street.
- 25. Scene V. Capulet's orchard.
- 26. Scene VI. Friar Laurence'...
- 27. ACT III. Scene I. A publi...
- 28. Scene II. Capulet's orchard.
- 29. Scene III. Friar Laurence...
- 30. Scene IV. Capulet's house
- 31. Scene V. Capulet's orchard.
- 32. ACT IV. Scene I. Friar La...
- 33. Scene II. Capulet's house.
- 34. Scene III. Juliet's chamber.
- 35. Scene IV. Capulet's house.
- 36. Scene V. Juliet's chamber.
- 37. ACT V. Scene I. Mantua. A...
- 38. Scene II. Verona. Friar L...
session fragment
The layout sessions_frag.xml
specifies several buttons, notably @+id/takePictureBtn
and (less interestingly) @+id/focusBtn
. MainActivity.java
attaches a Tab Listener as follows:
bar.addTab(bar.newTab().setText("Session").setTabListener(new MyTabListener(new TabletSessionFragment())));
Inside TabletSessionFragment
we see how the Capture button is handled:
public void onTakePictureClicked(View view) {
// TODO necessary
//liveView.setLiveViewData(null);
camera().capture();
justCaptured = true;
handler.postDelayed(justCapturedResetRunner, 500);
}
For canon EOS, the capture()
method results in
@Override
public void capture() {
if (isBulbCurrentShutterSpeed()) {
queue.add(new SimpleCommand(this, cameraIsCapturing ? Operation.EosBulbEnd : Operation.EosBulbStart));
} else {
queue.add(new EosTakePictureCommand(this));
}
}
In other words, either a SimpleCommand
or an EosTakePictureCommand
added to the queue for the USB bus. Neither of these command do much themselves, for example:
public SimpleCommand(PtpCamera camera, int operation) {
super(camera);
this.operation = operation;
}
And the super
call goes to
public Command(PtpCamera camera) {
this.camera = camera;
}
This leads us to suspect that there is something polling the USB queue. This poller would pick up the above Command
and send that into the USB queue. What is that? Our first suspect is WorkerThread
. The below indefinite loop does little to plead WorkerThread
's innocence:
while (true) {
synchronized (this) {
if (stop) {
break;
}
}
if (lastEventCheck + AppConfig.EVENTCHECK_PERIOD < System.currentTimeMillis()) {
lastEventCheck = System.currentTimeMillis();
PtpCamera.this.queueEventCheck();
}
PtpAction action = null;
try {
action = queue.poll(1000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
// nop
}
if (action != null) {
action.exec(this);
}
}
Indeed, when we connect a camera, we get log messages like:
.../com.remoteyourcam.usb I/PtpCamera﹕ Starting session for 04a9 327f
.../com.remoteyourcam.usb W/PtpCamera﹕ notifyWorkStarted()
Guilty as charged! Here are some of the commands (or actions) that are handled by action.exec()
- GetDeviceInfoCommand
- EosOpenSessionAction
- EosEventCheckCommand (many times)
- EosTakePictureCommand
- RetrievePictureAction
Such commands/actions are handled as follows:
@Override
public void handleCommand(Command command) {
if (AppConfig.LOG) {
Log.i(TAG, "handling command " + command.getClass().getSimpleName());
}
ByteBuffer b = smallIn;
b.position(0);
command.encodeCommand(b);
int outLen = b.position();
int res = connection.bulkTransferOut(b.array(), outLen, AppConfig.USB_TRANSFER_TIMEOUT);
if (res < outLen) {
onUsbError(String.format("Code CP %d %d", res, outLen));
return;
}
if (command.hasDataToSend()) {
b = ByteBuffer.allocate(connection.getMaxPacketOutSize());
b.order(ByteOrder.LITTLE_ENDIAN);
command.encodeData(b);
outLen = b.position();
res = connection.bulkTransferOut(b.array(), outLen, AppConfig.USB_TRANSFER_TIMEOUT);
if (res < outLen) {
onUsbError(String.format("Code DP %d %d", res, outLen));
return;
}
}
while (!command.hasResponseReceived()) {
int maxPacketSize = maxPacketInSize;
ByteBuffer in = smallIn;
in.position(0);
res = 0;
while (res == 0) {
res = connection.bulkTransferIn(in.array(), maxPacketSize, AppConfig.USB_TRANSFER_TIMEOUT);
}
if (res < 12) {
onUsbError(String.format("Couldn't read header, only %d bytes available!", res));
return;
}
int read = res;
int length = in.getInt();
ByteBuffer infull = null;
if (read < length) {
if (length > fullInSize) {
fullInSize = (int) (length * 1.5);
fullIn = ByteBuffer.allocate(fullInSize);
fullIn.order(ByteOrder.LITTLE_ENDIAN);
}
infull = fullIn;
infull.position(0);
infull.put(in.array(), 0, read);
maxPacketSize = bigInSize;
int nextSize = Math.min(maxPacketSize, length - read);
int nextSize2 = Math.max(0, Math.min(maxPacketSize, length - read - nextSize));
int nextSize3 = 0;
r1.queue(bigIn1, nextSize);
if (nextSize2 > 0) {
r2.queue(bigIn2, nextSize2);
}
while (read < length) {
nextSize3 = Math.max(0, Math.min(maxPacketSize, length - read - nextSize - nextSize2));
if (nextSize3 > 0) {
bigIn3.position(0);
r3.queue(bigIn3, nextSize3);
}
if (nextSize > 0) {
connection.requestWait();
System.arraycopy(bigIn1.array(), 0, infull.array(), read, nextSize);
read += nextSize;
}
nextSize = Math.max(0, Math.min(maxPacketSize, length - read - nextSize2 - nextSize3));
if (nextSize > 0) {
bigIn1.position(0);
r1.queue(bigIn1, nextSize);
}
if (nextSize2 > 0) {
connection.requestWait();
System.arraycopy(bigIn2.array(), 0, infull.array(), read, nextSize2);
read += nextSize2;
}
nextSize2 = Math.max(0, Math.min(maxPacketSize, length - read - nextSize - nextSize3));
if (nextSize2 > 0) {
bigIn2.position(0);
r2.queue(bigIn2, nextSize2);
}
if (nextSize3 > 0) {
connection.requestWait();
System.arraycopy(bigIn3.array(), 0, infull.array(), read, nextSize3);
read += nextSize3;
}
}
} else {
infull = in;
}
infull.position(0);
try {
command.receivedRead(infull);
infull = null;
} catch (RuntimeException e) {
// TODO user could send us some data here
if (AppConfig.LOG) {
Log.e(TAG, "Exception " + e.getLocalizedMessage());
e.printStackTrace();
}
onPtpError(String.format("Error parsing %s with length %d", command.getClass().getSimpleName(),
length));
}
}
}
One of these commands, RetrievePictureAction
, has our specific interest. This command is used to retrieve a photo capture by the camera (initiated on the camera shutter button). This command we have 'intercepted' and to write the captured photo to file.
All of this happens within the WorkerThread
. The question facing us is: how do we get an intent from the thread back to the Main UI?
