Table of Contents

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()

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?